#ifndef _ToneMapping_H_
#define _ToneMapping_H_

#pragma once
#include "LightingRender.h"

class ToneMapping {
protected:
	Float adaptedLum = 1.0f;

public:
	ToneMapping() {}
	~ToneMapping() {}

	void setAdaptedLum(Float adapted_lum) {
		adaptedLum = adapted_lum;
	}

	inline virtual Float map(Float color) const {
		return color * adaptedLum;
	}

	inline virtual void map(Color & color) const {
		color *= adaptedLum;
	}
};

class ReinhardToneMapping : public ToneMapping {
public:
	ReinhardToneMapping() {}
	~ReinhardToneMapping() {}

	inline Float map(Float color) const {
		const float MIDDLE_GREY = 1.0f;
		color *= MIDDLE_GREY / adaptedLum;
		return color / (1.0f + color);
	}

	inline void map(Color & color) const {
		const float MIDDLE_GREY = 1.0f;
		color *= MIDDLE_GREY / adaptedLum;
		color /= (color + Colors::White);
	}
};

class CEToneMapping : public ToneMapping {
public:
	CEToneMapping() {}
	~CEToneMapping() {}

	inline Float map(Float color) const {
		return 1 - exp(-adaptedLum * color);
	}

	inline void map(Color & color) const {
		color *= -adaptedLum;
		color = Colors::White - color.exp();
	}
};

class Uncharted2ToneMapping : public ToneMapping {
public:
	Uncharted2ToneMapping() {}
	~Uncharted2ToneMapping() {}

	inline Float F(Float x) const {
		const Float A = 0.22f;
		const Float B = 0.30f;
		const Float C = 0.10f;
		const Float D = 0.20f;
		const Float E = 0.01f;
		const Float F = 0.30f;

		return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;
	}

	inline Float map(Float color) const {
		const Float WHITE = 11.2f;
		return F(1.6f * adaptedLum * color) / F(WHITE);
	}

	inline void map(Color & color) const {
		const Float WHITE = 11.2f;
		color *= 1.6f * adaptedLum;
		color.r = F(color.r) / F(WHITE);
		color.g = F(color.g) / F(WHITE);
		color.b = F(color.b) / F(WHITE);
	}
};

class ACESToneMapping : public ToneMapping {
public:
	ACESToneMapping() {}
	~ACESToneMapping() {}

	inline Float map(Float color) const {
		const Float A = 2.51f;
		const Float B = 0.03f;
		const Float C = 2.43f;
		const Float D = 0.59f;
		const Float E = 0.14f;

		color *= adaptedLum;
		return (color * (A * color + B)) / (color * (C * color + D) + E);
	}

	inline void map(Color & color) const {
		const Float A = 2.51f;
		const Float B = 0.03f;
		const Float C = 2.43f;
		const Float D = 0.59f;
		const Float E = 0.14f;

		color *= adaptedLum;
		color.r = (color.r * (A * color.r + B)) / (color.r * (C * color.r + D) + E);
		color.g = (color.g * (A * color.g + B)) / (color.g * (C * color.g + D) + E);
		color.b = (color.b * (A * color.b + B)) / (color.b * (C * color.b + D) + E);
	}
};

#endif // _ToneMapping_H_